﻿using Devil;
using GameToolkit.BehaviourTree;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;

namespace GameToolkit.Editor
{
    public delegate bool SelectSearchEntryCallback<T>(T providor, SearchTreeEntry entry, SearchWindowContext context) where T: ISearchWindowProvider;

    //public interface ISearchNodeInfo
    //{
    //    JsonSerializedObject.TypeWrapper WrapType { get; }
    //    Texture2D Icon { get; }
    //    string Title { get; }
    //    string Descript { get; }
    //    string Namespace { get; }
    //}

    //public interface ISearchTreeLib
    //{
    //    ISearchNodeInfo GetNodeInfo(System.Type nodeType);
    //    ISearchWindowProvider GetSearchWindowProvider();
    //}


    public static class SearchTreeProviderUtils
    {
        //internal static Dictionary<string, ISearchTreeLib> mLibs = new Dictionary<string, ISearchTreeLib>();

        internal static SearchTreeEntry AppendToSearchTreeList(object userData, string title, IList<SearchTreeEntry> searchLst)
        {
            int startLv = 1;
            int startIndex = searchLst.Count;
            var path = title.Split('/');
            SearchTreeEntry et;
            for (int i = 1; i < searchLst.Count; i++)
            {
                et = searchLst[i];
                if (et.level == startLv && et.name == path[startLv - 1])
                {
                    startLv++;
                    startIndex = i + 1;
                }
                else if (et.level < startLv)
                {
                    break;
                }
            }
            var gsize = path.Length - 1;
            for (int i = startLv - 1; i < gsize; i++)
            {
                var grp = new SearchTreeGroupEntry(new GUIContent(path[i]), i + 1);
                if (startIndex < searchLst.Count)
                {
                    searchLst.Insert(startIndex, grp);
                    startIndex++;
                }
                else
                {
                    searchLst.Add(grp);
                    startIndex = searchLst.Count;
                }
            }
            et = new SearchTreeEntry(new GUIContent(path[gsize])) { level = path.Length };
            et.userData = userData;
            if (startIndex < searchLst.Count)
                searchLst.Insert(startIndex, et);
            else
                searchLst.Add(et);
            return et;
        }

        //public static ISearchTreeLib GetLib(string libName)
        //{
        //    ISearchTreeLib lib;
        //    if (mLibs.TryGetValue(libName, out lib))
        //        return lib;
        //    else
        //        return null;
        //}
    }

    public abstract class SearchTreeLib<TProvider> : ScriptableObject, ISearchWindowProvider 
        where TProvider : SearchTreeLib<TProvider>, ISearchWindowProvider
    {
        static Lib mLibInst = new Lib();

        public static IEnumerator LoadLib()
        {
            var inst = Get();
            mLibInst.LoadLib(inst);
            inst.mIsDirty = true;
            yield return ParallelUtils.Schedule(() =>
            {
                var noneIcon = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(NodeInfo.sNoneIcon));
                foreach (var entry in mLibInst.SearchTreeLst)
                {
                    if (entry.userData is NodeInfo ninfo)
                        entry.content.image = ninfo.Icon;
                    else if(entry is not SearchTreeGroupEntry)
                        entry.content.image = noneIcon;
                }
            }, true);
        }

        static TProvider mProvider;
        public static TProvider Get()
        {
            if (mProvider == null)
            {
                mProvider = ScriptableObject.CreateInstance<TProvider>();
                mProvider.hideFlags |= HideFlags.NotEditable | HideFlags.DontSave;
            }
            return mProvider;
        }

        public static Texture2D GetAssetIcon()
        {
            return mLibInst.Icon;
        }

        public static NodeInfo GetNodeInfo(System.Type type)
        {
            return mLibInst.GetNodeInfo(type);
        }

        public class NodeInfo //: ISearchNodeInfo
        {

            public static readonly string sNoneIcon = "daaf50bf019877d41a4e8f4bae43d3bb";
            public static readonly string sDefaultIcon = "95a4be866390b0c4fb8e35dddcaf8f3a";

            int mDepth;
            string mCategory;
            string mName;
            string mDesc;
            string mIconPath;
            Texture2D mIcon;
            JsonSerializedObject.TypeWrapper mWrapper;
            public Texture2D Icon
            {
                get
                {
                    if (mIcon == null && !string.IsNullOrEmpty(mIconPath))
                    {
                        if (Regex.IsMatch(mIconPath, "^[0123456789abcdef]{32}$", RegexOptions.IgnoreCase))
                            mIconPath = AssetDatabase.GUIDToAssetPath(mIconPath);
                        mIcon = AssetDatabase.LoadAssetAtPath<Texture2D>(mIconPath);
                        if (mIcon == null)
                            mIconPath = null;
                    }
                    return mIcon;
                }
            }
            public JsonSerializedObject.TypeWrapper WrapType => mWrapper;
            public string Title => mName;
            public string Descript => mDesc;
            //public string Category => mCategory;
            public string Namespace => mWrapper.WrapType.Namespace;
            public int Depth => mDepth;

            public NodeInfo(JsonSerializedObject.TypeWrapper wrapper)
            {
                mWrapper = wrapper;
                var tp = wrapper.WrapType;
                var ntype = wrapper.WrapType;
                var cate = ntype.GetCustomAttribute<CompositeCategoryAttribute>();
                var title = ntype.GetCustomAttribute<CompositeTitleAttribute>();
                var icon = ntype.GetCustomAttribute<CompositeIconAttribute>();
                if (title == null)
                {
                    mName = ParallelUtils.ToCamelName(ntype.Name, true);
                    mDesc = ntype.FullName;
                }
                else
                {
                    mName = title.Title;
                    mDesc = title.Description;
                    if (string.IsNullOrEmpty(mName))
                        mName = ParallelUtils.ToCamelName(ntype.Name, true);
                    if (string.IsNullOrEmpty(mDesc))
                        mDesc = ntype.FullName;
                }
                //var nodename = NodeTypeNames[(int)mNodeType];
                if (cate == null)
                {
                    var nspace = ntype.Namespace;
                    if (string.IsNullOrEmpty(nspace))
                        nspace = "Default";
                    else
                        nspace = nspace.Replace('.', '/');
                    mCategory = $"{nspace}/{mName} ({ntype.Name})";
                }
                else if (string.IsNullOrEmpty(cate.Category))
                {
                    mCategory = $"{mName} ({ntype.Name})";
                }
                else
                {
                    mCategory = $"{cate.Category}/{mName} ({ntype.Name})";
                }
                mDepth = 1;
                for (int i = mCategory.Length - 2; i > 0; i--)
                {
                    if (mCategory[i] == '/')
                        mDepth++;
                }
                if (icon == null || string.IsNullOrEmpty(icon.Icon))
                {
                    mIconPath = sDefaultIcon;
                }
                else
                {
                    mIconPath = icon.Icon;
                }
            }

            internal void AppendToSearchTreeList(IList<SearchTreeEntry> searchLst)
            {
                SearchTreeProviderUtils.AppendToSearchTreeList(this, mCategory, searchLst);
            }

        }

        class Lib //: ISearchTreeLib
        {
            Dictionary<System.Type, NodeInfo> mNodesLib = new Dictionary<System.Type, NodeInfo>();
            List<SearchTreeEntry> mLoadingSearchTree = new List<SearchTreeEntry>() { new SearchTreeGroupEntry(new GUIContent("Is Loading...")) };

            List<SearchTreeEntry> mSearchTreeLst;
            Texture2D mIcon;
            string mIconPath;
            public Texture2D Icon
            {
                get
                {
                    if(!string.IsNullOrEmpty(mIconPath) && mIcon == null)
                    {
                        if (Regex.IsMatch(mIconPath, "^[0123456789abcdef]{32}$", RegexOptions.IgnoreCase))
                            mIconPath = AssetDatabase.GUIDToAssetPath(mIconPath);
                        mIcon = AssetDatabase.LoadAssetAtPath<Texture2D>(mIconPath);
                        if (mIcon == null)
                            mIconPath = null;
                    }
                    return mIcon;
                }
            }

            public List<SearchTreeEntry> SearchTreeLst => mSearchTreeLst == null || mSearchTreeLst.Count == 0 ? mLoadingSearchTree : mSearchTreeLst;

            internal NodeInfo GetNodeInfo(System.Type type)
            {
                NodeInfo info;
                if (mNodesLib != null && mNodesLib.TryGetValue(type, out info))
                    return info;
                else
                    return null;
            }

            //ISearchNodeInfo ISearchTreeLib.GetNodeInfo(System.Type nodeType)
            //{
            //    return GetNodeInfo(nodeType);
            //}

            public ISearchWindowProvider GetSearchWindowProvider()
            {
                return Get();
            }

            internal void LoadLib(SearchTreeLib<TProvider> libProvider)
            {
                mIconPath = libProvider.Icon;
                JsonSerializedObject.InitWrappers();
                var lib = new Dictionary<System.Type, NodeInfo>();
                var lst = new List<JsonSerializedObject.TypeWrapper>();
                System.Action<JsonSerializedObject.TypeWrapper> wrapAction = (wrap) =>
                {
                    if (libProvider.IsNodeType(wrap.WrapType))
                        lst.Add(wrap);
                };
                JsonSerializedObject.VisitAllWrappers(wrapAction);
                var nodeLib = new List<SearchTreeEntry>();
                nodeLib.Add(new SearchTreeGroupEntry(new GUIContent(libProvider.SearchTreeTitle)));
                var noneTitle = libProvider.NameOfNone;
                if (!string.IsNullOrEmpty(noneTitle))
                {
                    SearchTreeProviderUtils.AppendToSearchTreeList(null, noneTitle, nodeLib);
                }
                for (int i = 0; i < lst.Count; i++)
                {
                    var info = libProvider.CreateNodeInfo(lst[i]);
                    lib[info.WrapType.WrapType] = info;
                    info.AppendToSearchTreeList(nodeLib);
                }
                mNodesLib = lib;
                //var coms = new MinHeap<NodeInfo>(lib.Count, (a, b) => a.Depth - b.Depth);
                //foreach (var info in lib.Values)
                //{
                //    coms.Add(info);
                //}
                ////foreach(var info in lib.Values)
                //while (coms.Count > 0)
                //{
                //    var info = coms.RemoveTop();
                //    info.AppendToSearchTreeList(nodeLib);
                //}
                if (nodeLib.Count == 1)
                {
                    nodeLib[0].content.text = "No Avaliable Nodes...";
                }
                mSearchTreeLst = nodeLib;
                //SearchTreeProviderUtils.mLibs[libProvider.GetType().Name] = this;
            }
        }

        protected virtual NodeInfo CreateNodeInfo(JsonSerializedObject.TypeWrapper wrapper)
        {
            return new NodeInfo(wrapper);
        }

        protected virtual string NameOfNone => null;

        public static string GetDescription(object nodeasset, bool getRuntimeInfo = true, bool includeTypeName = false)
        {
            if (nodeasset == null)
                return "<nothing>";
            if (getRuntimeInfo)
            {
                var desc = nodeasset is IAliveDescripter descript ? descript.GetDescription() : null;
                if (!string.IsNullOrEmpty(desc))
                {
                    if (includeTypeName)
                        return $"{desc} ({nodeasset.GetType().Name})";
                    else
                        return desc;
                }
            }
            var info = mLibInst.GetNodeInfo(nodeasset.GetType());
            if (info == null)
                return "<unknown>";
            if (includeTypeName)
                return $"{info.Descript} ({info.WrapType.WrapType.Name})";
            else
                return info.Descript;
        }

        bool mIsDirty = true;
        List<SearchTreeEntry> mTemporaryLst = new List<SearchTreeEntry>();
        FilterDelegate<SearchTreeEntry> mFilter;
        public abstract string Icon { get; }
        public FilterDelegate<SearchTreeEntry> Filter
        {
            get { return mFilter; }
            set
            {
                mIsDirty |= mFilter != value;
                mFilter = value;
            }
        }

        public SelectSearchEntryCallback<TProvider> OnSelectEntryAction { get; set; }
        SelectSearchEntryCallback<TProvider> mOnSelectEntryOnce;

        public void PerformOnce(SelectSearchEntryCallback<TProvider> action)
        {
            mOnSelectEntryOnce = action;
        }

        public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context)
        {
            if (!mIsDirty)
            {
                return mTemporaryLst;
            }
            mIsDirty = false;
            mTemporaryLst.Clear();
            mTemporaryLst.AddRange(mLibInst.SearchTreeLst);
            if (mFilter != null)
            {
                int maxLv = 1;
                for (int i = mTemporaryLst.Count - 1; i >= 0; i--)
                {
                    var item = mTemporaryLst[i];
                    if (item is SearchTreeGroupEntry)
                        continue;
                    if (!Filter(item))
                    {
                        mTemporaryLst.RemoveAt(i);
                    }
                    else if (item.level > maxLv)
                    {
                        maxLv = item.level;
                    }
                }
                // clean empty groups
                var nums = new int[maxLv];
                for (int i = mTemporaryLst.Count - 1; i > 0; i--)
                {
                    var item = mTemporaryLst[i];
                    if (item is SearchTreeGroupEntry)
                    {
                        if (nums[item.level - 1] == 0)
                        {
                            mTemporaryLst.RemoveAt(i);
                        }
                        if (item.level == 1)
                        {
                            for (int k = 0; k < nums.Length; k++)
                            {
                                nums[k] = 0;
                            }
                        }
                    }
                    else
                    {
                        for (int k = 0; k < item.level; k++)
                        {
                            nums[k]++;
                        }
                    }
                }
            }
            return mTemporaryLst;
        }

        public bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext context)
        {
            bool result = false;
            if (mOnSelectEntryOnce != null && mOnSelectEntryOnce((TProvider)this, entry, context))
                result = true;
            else if (OnSelectEntryAction != null && OnSelectEntryAction((TProvider)this, entry, context))
                result = true;
            else
                result = false;
            if (result)
                mOnSelectEntryOnce = null;
            return result;
        }

        public abstract string SearchTreeTitle { get; }
        public abstract bool IsNodeType(System.Type type);

    }
}
